/*
 * Copyright 2006-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.martinimix.service.syndication;

import java.io.IOException;
import java.io.Writer;
import java.util.List;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndFeedImpl;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedOutput;

import net.martinimix.context.event.syndication.SyndicationFeedPublishedEvent;
import net.martinimix.domain.syndication.SyndicationFeed;
import net.martinimix.domain.syndication.SyndicationFeedEntry;

/**
 * Provides an abstract feed publishing service implementation containing base
 * utility methods useful for classes wishing to implement a feed publishing service
 * using the <a href="https://rome.dev.java.net/">Project ROME</a> syndication framework.
 * 
 * 
 * @author Scott Rossillo
 *
 */
public abstract class AbstractRomeFeedPublishingService 
		implements SyndicationFeedPublishingService, ApplicationContextAware {
	
	private ApplicationContext applicationContext;

	/**
	 * Creates a ROME syndication feed from the given syndication feed bean.
	 * 
	 * @param feed the <code>SyndicationFeed</code> providing content for the returned
	 * <code>SyndFeed</code>
	 * 
	 * @return a <code>SyndFeed</code> containing the contents of the given <code>feed</code>
	 * 
	 */
	protected SyndFeed createSyndFeed(SyndicationFeed feed) {
	
		SyndFeed syndFeed = new SyndFeedImpl();
		List entries = feed.getEntries();
		
		syndFeed.setLink(feed.getLink());
		syndFeed.setTitle(feed.getTitle());
		syndFeed.setUri(createUri(feed, null));
		
		for(int i = 0; i < entries.size(); i++) {
			syndFeed.getEntries().add(createSyndFeedEntry(feed, (SyndicationFeedEntry)  entries.get(i)));
		}
		
		return syndFeed;
	}
	
	/**
	 * Creates a ROME syndication feed entry from the given feed and entry beans.
	 * 
	 * @param feed the <code>SyndicationFeed</code> containing the given entry
	 * 
	 * @param entry the <code>SyndicationFeedEntry</code> providing content to the created
	 * <code>SyndEntry</code>
	 * 
	 * @return a <code>SyndEntry</code> created from the given <code>entry</code>
	 */
	protected SyndEntry createSyndFeedEntry(SyndicationFeed feed, SyndicationFeedEntry entry) {
		
		SyndEntry syndEntry = new SyndEntryImpl();
		SyndContent summary = new SyndContentImpl();
		
		syndEntry.setUri(createUri(feed, entry));
		
		syndEntry.setLink(entry.getLink());
		syndEntry.setTitle(entry.getTitle());
		summary.setValue(entry.getSummary());
		syndEntry.setDescription(summary);
		
		syndEntry.setUpdatedDate(entry.getCreateDate().getTime());
		
		return syndEntry;
	}
	
	/**
	 *	TODO: should be protected?
	 * 
	 * @param feed
	 * @param entry
	 * @return
	 */
	private String createUri(SyndicationFeed feed, SyndicationFeedEntry entry) {
		
		if(feed.getLink() == null) {
			throw new IllegalStateException("Feed link must be set before a uri can be created!");
		}
		
		StringBuffer uri = new StringBuffer();
		
		uri.append(feed.getLink());
		
		if(!feed.getLink().endsWith("/")) {
			uri.append('/');
		}
		
		uri.append("feed/");
		
		if(entry == null) {
			uri.append(feed.getId());
		} else {
			uri.append("entry/").append(entry.getId());
		}
		
		return uri.toString();
	}
	
	/**
	 * Returns an XML version of the given feed.
	 * 
	 * @param feed the <code>SyndicationFeed</code> to be returned as XML
	 * 
	 * @return the XML serialized version of the given <code>feed</code>
	 * 
	 * @throws FeedException if an error occurs while converting the given
	 * <code>feed</code> to XML
	 */
	protected String createXML(SyndicationFeed feed) throws FeedException {
		
		return createXML(createSyndFeed(feed));
	}
	
	/**
	 * Returns an XML version of the given feed.
	 * 
	 * @param feed the <code>SyndFeed</code> to be returned as XML
	 * 
	 * @return the XML serialized version of the given <code>feed</code>
	 * 
	 * @throws FeedException if an error occurs while converting the given
	 * <code>feed</code> to XML
	 */
	protected String createXML(SyndFeed feed) throws FeedException {
		
		SyndFeedOutput out = new SyndFeedOutput();
		
		if(feed.getFeedType() == null) {
			feed.setFeedType("atom_1.0");
		}
		
		return out.outputString(feed);
	}
	
	/**
	 * Writes the given feed as XML to the given writer.
	 * 
	 * @param feed the <code>SyndicationFeed</code> to be written
	 * 
	 * @param writer the <code>Writer</code> to which the given <code>feed</code>
	 * will be written
	 * 
	 * @throws FeedException if the given <code>feed</code> is invalid
	 * 
	 * @throws IOException if an error occurs while writing to the given <code>writer</code>
	 */
	protected void outputXML(SyndicationFeed feed, Writer writer) throws FeedException, IOException {
		
		outputXML(createSyndFeed(feed), writer);
	}
	
	/**
	 * Writes the given feed as XML to the given writer.
	 * 
	 * @param feed the <code>SyndFeed</code> to be written
	 * 
	 * @param writer the <code>Writer</code> to which the given <code>feed</code>
	 * will be written
	 * 
	 * @throws FeedException if the given <code>feed</code> is invalid
	 * 
	 * @throws IOException if an error occurs while writing to the given <code>writer</code>
	 */
	protected void outputXML(SyndFeed feed, Writer writer) throws FeedException, IOException {
		
		SyndFeedOutput out = new SyndFeedOutput();
		
		if(feed.getFeedType() == null) {
			feed.setFeedType("atom_1.0");
		}
		
		out.output(feed, writer);
	}

	/**
	 * Publishes the given feed by calling {@link #publishInternal(SyndicationFeed)} and
	 * fires an {@link SyndicationFeedPublishedEvent} upon successful publish.
	 * 
	 * @see #publishInternal(SyndicationFeed)
	 */
	public final void publish(SyndicationFeed feed) throws SyndicationFeedPublishFailedException {
		
		String url = publishInternal(feed);
		
		applicationContext.publishEvent(new SyndicationFeedPublishedEvent(this, feed, url));
	}
	
	/**
	 * Publishes the given feed.
	 * 
	 * @param feed the <code>SyndicationFeed</code> to publish
	 * 
	 * @return a <code>String</code> containing the externally available URL to which
	 * the given <code>feed</code> was published; may not be <code>null</code>
	 * 
	 * @throws SyndicationFeedPublishFailedException
	 */
	public abstract String publishInternal(SyndicationFeed feed) throws SyndicationFeedPublishFailedException;

	/* (non-Javadoc)
	 * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
	 */
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		
		this.applicationContext = applicationContext;
	}
	
}
